using Gamelogic.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Assertions;

public class Line
{
   public Vector3 offset;
   public Vector3 direction;

   public Vector3 Evaluate(float t)
   {
      return offset + direction * t;
   }

   override public string ToString()
   {
      return direction.ToString() + "[t]" + " + " + offset;
   }

}

public class MeshData
{
   public List<Vector3> vertices;
   public List<int> triangles;
   public List<Vector2> uvs;
   public List<Vector3> normals;

   public static MeshData Combine(List<MeshData> meshes)
   {
      MeshData combinedMesh = new MeshData
      {
         vertices = new List<Vector3>(),
         triangles = new List<int>(),
         uvs = new List<Vector2>(),
         normals = new List<Vector3>()
      };

      int vertexIndexOffset = 0;

      foreach(var mesh in meshes)
      {
         combinedMesh.vertices.AddRange(mesh.vertices);
         combinedMesh.triangles.AddRange(mesh.triangles.Select(index => index + vertexIndexOffset));

         if(mesh.uvs != null)
         {
            combinedMesh.uvs.AddRange(mesh.uvs);
         }

         if(mesh.normals != null)
         {
            combinedMesh.normals.AddRange(mesh.normals);
         }

         vertexIndexOffset += mesh.vertices.Count;
      }

      return combinedMesh;
   }
}

public static class MeshBuilderUtils
{
   private const float ParallelThreshold = 0.01f;

   public const float Right1 = Mathf.PI / 2;
   public const float Right2 = Mathf.PI;
   public const float Right3 = 3 * Mathf.PI / 2;
   public const float Right4 = 2 * Mathf.PI;

   public static bool IsParallel(Vector3 direction0, Vector3 direction1)
   {
      return Vector3.Cross(direction0.normalized, direction1.normalized).magnitude < ParallelThreshold;
   }
   public static bool IsParallel(Line line0, Line line1)
   {
      return IsParallel(line0.direction, line1.direction);
   }

   private static Tuple<float, float> SolveLinearEquation(
      float a0, float b0, float c0,
      float a1, float b1, float c1
      )
   {
      float v = (a1 * c0 - a0 * c1) / (a0 * b1 - a1 * b0);
      float t = (-c0 - b0 * v) / a0;

      return new Tuple<float, float>(t, v);
   }

   public static Vector2 Circle2(float angle)
   {
      return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
   }

   public static Vector3 CircleXY(float angle)
   {
      return new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0);
   }

   public static Vector3 GetIntersection(Line line0, Line line1)
   {
      Assert.IsTrue(!IsParallel(line0, line1), "Lines are (almost) parallel.");
      
      var solutions = SolveLinearEquation(
         Vector3.Dot(line0.direction, line0.direction),
         -Vector3.Dot(line1.direction, line0.direction),
         Vector3.Dot(line0.offset - line1.offset, line0.direction),

         Vector3.Dot(line0.direction, line1.direction),
         -Vector3.Dot(line1.direction, line1.direction),
         Vector3.Dot(line0.offset - line1.offset, line1.direction)
      );

      var intersection = line0.Evaluate(solutions.Item1);

      return intersection;
   }

   public static float GetAngleBetween(float startAngle, float endAngle, bool anticlockwise)
   {
      if (anticlockwise && endAngle < startAngle)
      {
         endAngle += Right4;
      }

      if (!anticlockwise && startAngle < endAngle)
      {
         startAngle += Right4;
      }

      float angleDifference = endAngle - startAngle;

      return angleDifference;
   }

   public static float AreaOfTriangle(Vector3 sideAB, Vector3 sideAC)
   {
      return 0.5f * Vector3.Cross(sideAB, sideAC).magnitude;
   }

   public static List<Vector3> QuadVertices()
   {
      return new List<Vector3>
      {
         new Vector3(-1, 1, 0),
         new Vector3(-1, -1, 0),
         new Vector3(1, -1, 0),
         new Vector3(1, 1, 0),

      };
   }

   public static List<int> QuadTriangles(
      int corner0,
      int corner1,
      int corner2,
      int corner3
      )
   {
      return new List<int> 
      { 
         corner0,
         corner2,
         corner1,

         corner2,
         corner0,
         corner3,
      };
   }

   public static List<Vector2> QuadUvs()
   {
      return new List<Vector2>
      {
         new Vector2(0, 1),
         new Vector2(1, 1),
         new Vector2(1, 0),
         new Vector2(0, 0)
      };
   }

   public static List<Vector3> SectorVertices(
      float angle, int triangleCount)
   {
      return SectorVertices(Vector3.zero, 1, 0, angle, true, triangleCount);
   }

   public static List<Vector3> SectorVertices(
      Vector3 center,
      float radius,
      float startAngle,
      float endAngle,
      bool anticlockwise,
      int triangleCount)
   {
      var vertices = new List<Vector3>();
      vertices.Add(center);

      startAngle = GLMathf.FloorMod(startAngle, MeshBuilderUtils.Right4);
      endAngle = GLMathf.FloorMod(endAngle, MeshBuilderUtils.Right4);

      if (anticlockwise && endAngle < startAngle)
      {
         endAngle += MeshBuilderUtils.Right4;
      }

      if (!anticlockwise && startAngle < endAngle)
      {
         startAngle += MeshBuilderUtils.Right4;
      }

      float angleDifference = endAngle - startAngle;
      float triangleAngle = angleDifference / triangleCount;

      for (int i = 0; i < triangleCount + 1; i++)
      {
         float theta = triangleAngle * i + startAngle;

         vertices.Add(center + radius * MeshBuilderUtils.CircleXY(theta));
      }

      return vertices;
   }

   public static List<int> SectorTriangles(
   int start,
   int triangleCount,
   bool anticlockwise)
   {
      var triangles = new List<int>();

      for (int i = 0; i < triangleCount; i++)
      {
         if (anticlockwise)
         {
            triangles.Add3(start, start + i + 2, start + i + 1);
         }
         else
         {
            triangles.Add3(start, start + i + 1, start + i + 2);
         }

      }

      return triangles;
   }

   public static List<Vector2> SectorUvs(
      Vector2 center,
      float radius,
      float startAngle,
      float endAngle,
      int triangleCount,
      bool anticlockwise
      )
   {
      var uvs = new List<Vector2>();

      uvs.Add(center);

      startAngle = GLMathf.FloorMod(startAngle, MeshBuilderUtils.Right4);
      endAngle = GLMathf.FloorMod(endAngle, MeshBuilderUtils.Right4);

      if (anticlockwise && endAngle < startAngle)
      {
         endAngle += MeshBuilderUtils.Right4;
      }

      if (!anticlockwise && startAngle < endAngle)
      {
         startAngle += MeshBuilderUtils.Right4;
      }

      float angleDifference = endAngle - startAngle;
      float triangleAngle = angleDifference / triangleCount;

      for (int i = 0; i < triangleCount + 1; i++)
      {
         float theta = triangleAngle * i + startAngle;

         uvs.Add(center + radius * MeshBuilderUtils.Circle2(theta));
      }

      return uvs;
   }

   //Assumes a set of vertices in the XY plane
   public static List<Vector2> GetStandardUvsXY(
      List<Vector3> vertices,
      bool preserveAspectRatio,
      bool mapOriginToCenter
   )
   {
      var boundingBox = GetBoundingBoxXY(vertices, mapOriginToCenter);
      var map = GetStandardUvMapXY(boundingBox, preserveAspectRatio, mapOriginToCenter);

      return vertices.Select(map).ToList();
   }
   
   private static Rect GetBoundingBoxXY(List<Vector3> vertices, bool mapOriginToCenter)
   {
      var anchor = vertices[0];
      var extent = vertices[0];

      foreach (var vertex in vertices.Skip(1))
      {
         if (vertex.x < anchor.x)
         {
            anchor.x = vertex.x;
         }

         else if (vertex.x > extent.x)
         {
            extent.x = vertex.x;
         }

         if (vertex.y < anchor.y)
         {
            anchor.y = vertex.y;
         }

         else if (vertex.y > extent.y)
         {
            extent.y = vertex.y;
         }
      }

      if (mapOriginToCenter)
      {
         anchor.x = Mathf.Min(anchor.x, -extent.x);
         anchor.y = Mathf.Min(anchor.y, -extent.y);

         extent.x = Mathf.Max(extent.x, -anchor.x);
         extent.y = Mathf.Max(extent.y, -anchor.y);
      }

      var size = extent - anchor;

      return new Rect(anchor, size);

   }

   private static Func<Vector3, Vector2> GetStandardUvMapXY(
      Rect boundingBox,
      bool preserveAspectRatio,
      bool mapOriginToCenter)
   {
      Vector2 anchor = boundingBox.position;
      Vector2 size = boundingBox.size;

      if (preserveAspectRatio)
      {
         if (size.x < size.y)
         {
            size = new Vector3(size.y, size.y, 0);
         }
         else
         {
            size = new Vector3(size.x, size.x, 0);
         }
      }

      if (mapOriginToCenter)
      {
         return v => new Vector2(v.x / size.x + 0.5f, v.y / size.y + 0.5f);
      }
      else
      {
         return v => new Vector2((v.x - anchor.x) / size.x, (v.y - anchor.y) / size.y);
      }
   }

   public static int GetTriangleCount(float angle, float trianglesPerRevolution)
   {
      return Mathf.CeilToInt(angle / (2 * Mathf.PI) * trianglesPerRevolution);
   }
}

public static class MeshBuilderExtensions
{
   public static List<T> Add2<T>(this List<T> list, T index0, T item1)
   {
      list.Add(index0);
      list.Add(item1);

      return list;
   }

   public static List<T> Add3<T>(this List<T> list, T item0, T item1, T item2)
   {
      list.Add(item0);
      list.Add(item1);
      list.Add(item2);

      return list;
   }

   public static List<U> Differences<T, U>(this List<T> list, Func<T, T, U> difference, bool loop)
   {
      var result = new List<U>();

      for(int i = 0; i < list.Count - 1; i++)
      {
         result.Add(difference(list[i], list[i + 1]));
      }

      if (loop)
      {
         result.Add(difference(list[list.Count - 1], list[0]));
      }

      return result;
   }

   /*
      Gets the vector's angle in the XY plane. 
    */
   public static float Atan2XY(this Vector3 v)
   {
      return Mathf.Atan2(v.y, v.x);
   }

}


public enum GLColor
{
   Red,
   Orange,
   Yellow,
   Green,
   Blue,
   Purple,
   Magenta,
   Black,
   White
}

[Serializable]
public class GeometryDebug
{
   public List<Vector3> dotPositions;
   public List<GLColor> dotColors;
   public List<Vector3> arrowPositions;
   public List<Vector3> arrowDirections;
   public List<GLColor> arrowColors;

   public List<Color> colorMap = new List<Color>
   {
      Color255(255, 70, 70),
      Color255(255, 150, 0),
      Color255(255, 255, 60),
      Color255(150, 220, 0),
      Color255(25, 174, 255),
      Color255(186, 0, 255),
      Color255(255, 0, 193),
      Color255(0, 0, 0),
      Color255(255, 255, 255),
   };

   private static Color Color255(int r, int g, int b, int a = 255)
   {
      return new Color(r / 255f, g / 255f, b / 255f, a / 255f);
   }

   public float Radius
   {
      get; set;
   } = 0.1f;

   public GeometryDebug()
   {
      dotPositions = new List<Vector3>();
      dotColors = new List<GLColor>();

      arrowPositions = new List<Vector3>();
      arrowDirections = new List<Vector3>();
      arrowColors = new List<GLColor>();
   }

   public void Clear()
   {
      dotPositions.Clear();
      dotColors.Clear();

      arrowPositions.Clear();
      arrowDirections.Clear();
      arrowColors.Clear();
   }

   public void AddDotXY(Vector3 position, GLColor color)
   {
      dotPositions.Add(position);
      dotColors.Add(color);
   }

   public void AddArrow(Vector3 position, Vector3 direction, GLColor color)
   {
      arrowPositions.Add(position);
      arrowDirections.Add(direction);
      arrowColors.Add(color);
   }

   public void Draw(Transform transform, MeshType meshType = MeshType.XYZ)
   {
      for(int i = 0; i < dotPositions.Count; i++)
      {
         var position = transform.TransformPoint(dotPositions[i]);
         float radius = transform.lossyScale.magnitude * Radius;
         Color color = colorMap[(int)dotColors[i]];

         DrawDot(position, radius, color, meshType);
      }

      for(int i = 0; i < arrowPositions.Count; i++)
      {
         var position = transform.TransformPoint(arrowPositions[i]);
         var direction = transform.TransformDirection(arrowDirections[i]);
         float size = transform.lossyScale.magnitude * 0.2f;

         Color color = colorMap[(int)arrowColors[i]];

         DrawArrow(position, direction, size, color);
      }
   }

   public static void DrawArrow(Vector3 position, Vector3 direction, float size, Color color)
   {
#if UNITY_EDITOR
        Handles.color = color;
      Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(direction), size, EventType.Repaint);
#endif
    }

   public static void DrawDot(Vector3 position, float radius, Color color, MeshType meshType)
   {
      switch (meshType)
      {
         case MeshType.XYZ:
            Gizmos.color = color;
            Gizmos.DrawWireSphere(position, radius);
         break;
#if UNITY_EDITOR
            case MeshType.XY:
            Handles.color = color;
            Handles.DrawSolidDisc(position, Vector3.forward, radius);
         break;

         case MeshType.XZ:
            Handles.color = color;
            Handles.DrawSolidDisc(position, Vector3.forward, radius);
         break;
#endif
        }
   }
}